#include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <vector>

#define MAX_LENGTH 1024

using namespace std;

class Usr
{
public:
    Usr(string ip, uint16_t port, int sock)
        : _ip(ip), _port(port), _sockfd(sock)
    {
    }
    ~Usr()
    {
    }

    void UsrInfoMod(string name) // 修改用户昵称
    {
        _name = name;
    }

    void UpNet() // 上线
    {
        _IsNet = true;
    }

    void DowNet() // 下线
    {
        _IsNet = false;
    }

    bool IsNet() // 返回用户状态
    {
        return _IsNet;
    }
    string _name;        // 该用户聊天室昵称
    string _ip;          // 该用户的ip地址
    uint16_t _port;      // 该用户的端口号
    bool _IsNet = false; // 该用户是否在线
    int _sockfd;         // 该用户在服务器中对应的套接字
};

class ChatRoom
{
public:
    ChatRoom(string ip, uint16_t port)
        : _ip(ip), _port(port)
    {
        int Check = epoll_create(5);
        if (Check < 0)
        {
            cout << "EPOLL_CREATE_ERR " << strerror(errno) << endl;
            exit(1);
        }
        _epfd = Check;
    }
    //初始化成员函数
    void Init()
    {
        
        int Check = socket(AF_INET, SOCK_STREAM, 0);
        if (Check < 0)
        {
            cout << "SOCKET_ERR " << strerror(errno) << endl;
            exit(1);
        }
        _listen_sock = Check;

        sockaddr_in addr;
        addr.sin_addr.s_addr = inet_addr(_ip.c_str());
        addr.sin_family = AF_INET;
        addr.sin_port = htons(_port);

        Check = bind(_listen_sock, (sockaddr *)&addr, sizeof(addr));
        if (Check < 0)
        {
            cout << "BIND_ERR " << strerror(errno) << endl;
            exit(1);
        }

        Check = listen(_listen_sock, 5);
        if (Check < 0)
        {
            cout << "LISTEN_ERR " << strerror(errno) << endl;
            exit(1);
        }
    }

    void Start()
    {
        // 创建监听事件数组
        epoll_event events[MAX_LENGTH];
        // 将待监听事件与listen套接字放进去
        //ADDEVENT本质就是一个位操作函数。
        ADDEVENT(_listen_sock, EPOLLIN);
        while (true)
        {
            int num = epoll_wait(_epfd, events, MAX_LENGTH, 0);
            {
                if (num < 0)
                {
                    cout << "EPOLL_WAIT_ERR " << strerror(errno) << endl;
                    exit(1);
                }
                else if (num > 0)
                {
                    int Sock;
                    for (int i = 0; i < num; i++)
                    {
                        //如果是listen套接字有连接接入。
                        if (events[i].data.fd == _listen_sock)
                        {
                            sockaddr_in addr;
                            memset(&addr, 0, sizeof(addr));
                            socklen_t len;
                            Sock = accept(_listen_sock, (sockaddr *)&addr, &len);
                            //
                            // cout << inet_ntoa(addr.sin_addr) << " " << ntohs(addr.sin_port) << " 已连入 " << endl;
                            cout << addr.sin_addr.s_addr << endl;
                            cout << "ip: " << inet_ntoa(addr.sin_addr) << "端口号: " << ntohs(addr.sin_port) << "发起连接" << endl;
                            if (Sock < 0)
                            {
                                cout << "ACCEPT_ERR " << strerror(errno) << endl;
                                exit(1);
                            }
                            Usr tmp{inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), Sock};

                            _fds.push_back(tmp);
                            ADDEVENT(Sock, EPOLLIN);
                            //
                            cout << "已成功添加: " << Sock << "套接字" << endl;
                        }
                        //否则就执行普通的通信任务。
                        else
                        {
                            Sock = events[i].data.fd;
                            char buffer[1024];
                            int No; // 表示_fds中第No个用户发送的数据
                            int ret = recv(Sock, buffer, 1023, 0);
                            buffer[ret] = 0;
                            //执行用户掉线任务。
                            if (ret == 0)
                            {
                                struct epoll_event events;
                                events.data.fd = Sock;
                                events.events = EPOLLIN;
                                cout << "套接字 " << Sock << " 已离线" << endl;
                                // 将该套接字从epoll中删除
                                epoll_ctl(_epfd, EPOLL_CTL_DEL, Sock, &events);

                                vector<Usr>::iterator it = _fds.begin();
                                while(it != _fds.end())
                                {
                                    if(it->_sockfd == Sock) 
                                    {
                                        _fds.erase(it);
                                        break;
                                    }
                                }
                                break;
                            }
                            // 找到哪个客户端发送的数据
                            for (int i = 0; i < _fds.size(); i++)
                            {
                                if (_fds[i]._sockfd == Sock)
                                {
                                    No = i;
                                    break;
                                }
                            }
                            // 用户上线操作
                            if (!strcmp(string{buffer, buffer + 6}.c_str(), "online"))
                            {
                                _fds[No]._name = string{buffer + 6, buffer + ret};
                                _fds[No].UpNet();
                                SendAll(No, _fds[No]._name + " 加入聊天室");
                            }
                            // 用户离线操作
                            else if (!strcmp(string{buffer, buffer + 7}.c_str(), "offline"))
                            {
                                _fds[No].DowNet();
                                 SendAll(No, _fds[No]._name + " 离开聊天室");
                            }
                            else if(_fds[No]._name.empty())
                            {
                                string Buf = "请您使用“online昵称”注册上号";
                                send(_fds[No]._sockfd, Buf.c_str(), Buf.size(), 0);
                            }
                            // 转发用户发送的消息
                            else
                            {
                                //
                                SendAll(No, buffer);
                            }
                            cout << " ip地址: " << _fds[No]._ip << " 端口号: " << _fds[No]._port << " 昵称: " << _fds[No]._name << " 发送: " << buffer << endl;
                        }
                    }
                }
            }
        }
    }

private:
    // 广播数据
    //参数一：发送数据的客户端
    //参数2：即将广播的数据内容
    void SendAll(int No, string buffer)
    {
        string Buf = _fds[No]._name + "发送数据 》》》\r\n\t\t" + buffer;
        for (int j = 0; j < _fds.size(); j++)
        {
            if (j != No && !_fds[j]._name.empty())
            {
                int ret = send(_fds[j]._sockfd, Buf.c_str(), Buf.size(), 0);
                if (ret < 0)
                {
                    cout << "SEND_ERR " << strerror(errno) << endl;
                    exit(1);
                }
            }
        }
    }

    // fd为将要被添加监听的文件描述符
    // event是被监听的读事件或者写事件或者错误事件
    void ADDEVENT(int fd, uint32_t event)
    {
        struct epoll_event events;
        events.data.fd = fd;
        events.events = event; // 表示关心其读事件或者写事件、错误事件
        // EPOLL_CTL_ADD表示想要加入数据
        epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &events);
    }

    int _listen_sock; // 监听套接字
    string _ip;       // 当前主机的ip 0.0.0.0
    uint16_t _port;   // 当前程序的端口号
    int _epfd;        // epoll对应的对象
    vector<Usr> _fds; // 加入聊天室的用户套接字全部保存，即将用户使用vector管理起来
};